﻿using System.Globalization;
using FreeRedis;
using Newtonsoft.Json;

namespace Md.Im.Service;

public class ImClient
{
	protected RedisClient _redis;
	protected string[] _servers;
	protected string _redisPrefix;
	protected string _pathMatch;

	public EventHandler<ImSendEventArgs> OnSend;

	public ImClient(ImClientOptions options)
	{
		if (options.Redis == null) throw new ArgumentException("ImClientOptions.Redis 参数不能为空");
		if (options.Servers.Any() == false) throw new ArgumentException("ImClientOptions.Servers 参数不能为空");

		_redis = options.Redis;
		_servers = options.Servers;
		_redisPrefix = $"mdim{options.PathMatch.Replace('/', '_')}";
		_pathMatch = options.PathMatch ?? "/ws";
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="clientId"></param>
	/// <returns></returns>
	protected string SelectServer(Guid clientId)
	{
		var servers_idx = int.Parse(clientId.ToString("N").Substring(28), NumberStyles.HexNumber) % _servers.Length;
		if (servers_idx >= _servers.Length) servers_idx = 0;
		return _servers[servers_idx];
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="clientId"></param>
	/// <returns></returns>
	public string[] GetChanListByClientId(Guid clientId)
	{
		return _redis.HKeys($"{_redisPrefix}Client{clientId}");
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="clientId"></param>
	/// <param name="chans"></param>
	public void LeaveChan(Guid clientId, params string[] chans)
	{
		if (chans?.Any() != true) return;
		using (var pipe = _redis.StartPipe())
		{
			foreach (var chan in chans)
			{
				pipe.HDel($"{_redisPrefix}Chan{chan}", clientId.ToString());
				pipe.HDel($"{_redisPrefix}Client{clientId}", chan);
				pipe.Eval($"if redis.call('HINCRBY', KEYS[1], '{chan}', '-1') <=0 then redis.call('HDEL', KEYS[1], '{chan}') end return 1", new[] { $"{_redisPrefix}ListChan" });
			}
			pipe.EndPipe();
		}
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="clientId"></param>
	/// <param name="userId"></param>
	/// <param name="clientMetaData"></param>
	/// <returns></returns>
	public string PrevConnectServer(Guid clientId, int userId, string clientMetaData)
	{
		var server = SelectServer(clientId);
		var token = $"{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}{Guid.NewGuid()}".Replace("-", "");
		_redis.Set($"{_redisPrefix}Token{token}", JsonConvert.SerializeObject((clientId, clientMetaData)), 10000);
		_redis.Set($"{_redisPrefix}_user_{userId}", clientId);
		return $"ws://{server}{_pathMatch}?token={token}";
	}

	/// <summary>
	/// 
	/// </summary>
	/// <param name="senderClientId"></param>
	/// <param name="receiveClientId"></param>
	/// <param name="message"></param>
	/// <param name="receipt"></param>
	public void SendMessage(Guid senderClientId, IEnumerable<Guid> receiveClientId, object message, bool receipt = false)
	{
		receiveClientId = receiveClientId.Distinct().ToArray();

		Dictionary<string, ImSendEventArgs> redata = new Dictionary<string, ImSendEventArgs>();

		foreach (var uid in receiveClientId)
		{
			string server = SelectServer(uid);

			if (redata.ContainsKey(server) == false) redata.Add(server, new ImSendEventArgs(server, senderClientId, message, receipt));

			redata[server].ReceiveClientId.Add(uid);
		}

		var messageJson = JsonConvert.SerializeObject(message);

		foreach (var sendArgs in redata.Values)
		{
			OnSend?.Invoke(this, sendArgs);
			_redis.Publish($"{_redisPrefix}Server{sendArgs.Server}", JsonConvert.SerializeObject((senderClientId, sendArgs.ReceiveClientId, messageJson, sendArgs.Receipt)));
		}
	}

	public void SendChanMessage(Guid senderClientId, string chan, object message)
	{
		var websocketIds = _redis.HKeys($"{_redisPrefix}Chan{chan}");
		SendMessage(Guid.Empty, websocketIds.Where(a => !string.IsNullOrEmpty(a)).Select(a => Guid.TryParse(a, out var tryuuid) ? tryuuid : Guid.Empty).ToArray(), message);
	}

    /// <summary>
    /// 
    /// </summary>
    /// <param name="onLine"></param>
    /// <param name="offLine"></param>
    public void EventBus(Action<(Guid clientId, string clientMetaData)> onLine, Action<(Guid clientId, string clientMetaData)> offLine)
	{
		var chanOnline = $"evt_{_redisPrefix}OnLine";
		var chanOffline = $"evt_{_redisPrefix}OffLine";
		_redis.Subscribe(new[] { chanOnline, chanOffline }, (chan, msg) =>
		{
			if (chan == chanOnline) onLine(JsonConvert.DeserializeObject<(Guid clientId, string clientMetaData)>(msg as string));
			if (chan == chanOffline) offLine(JsonConvert.DeserializeObject<(Guid clientId, string clientMetaData)>(msg as string));
		});
	}

    /// <summary>
    /// 获取所在线客户端id
    /// </summary>
    /// <returns></returns>
    public IEnumerable<Guid> GetClientListByOnline()
    {
        return _redis.HKeys($"{_redisPrefix}OnLine").Select(a => Guid.TryParse(a, out var tryguid) ? tryguid : Guid.Empty).Where(a => a != Guid.Empty);
    }

	/// <summary>
	/// 加入频道，每次上线都必须重新加入
	/// </summary>
	/// <param name="clientId">客户端id</param>
	/// <param name="chan">频道名称</param>
	public void JoinChan(Guid clientId, string chan)
	{
		using (var pipe = _redis.StartPipe())
		{
			pipe.HSet($"{_redisPrefix}Chan{chan}", clientId.ToString(), 0);
			pipe.HSet($"{_redisPrefix}Client{clientId}", chan, 0);
			pipe.HIncrBy($"{_redisPrefix}ListChan", chan, 1);
			pipe.EndPipe();
		}
	}
}

